<!DOCTYPE html>
<html>

<head>
    <!-- Foundation CSS framework (Bootstrap and jQueryUI also supported) -->
    <link rel='stylesheet' href='css/foundation.min.css'>
    <!-- Font Awesome icons (Bootstrap, Foundation, and jQueryUI also supported) -->
    <link rel='stylesheet' href='css/font-awesome.min.css'>
    <style>
    [class*="foundicon-"] {
        font-family: GeneralFoundicons;
        font-style: normal;
    }
    .drop-define {
        border: 5px dashed transparent;
    }
    .active {
        opacity: 0.8;
        background-color: #cbe9ef;
        border: 5px dashed #9c9c9c;
    }
    .row-width {
        max-width: 78rem;
    }
    </style>
    <script src="./jsoneditor.min.js"></script>
    <!-- Please copy the following files into here: -->
    <script src="./Long.min.js"></script>
    <!-- https://raw.github.com/dcodeIO/Long.js/master/dist/Long.min.js -->
    <script src="./ByteBufferAB.min.js"></script>
    <!-- https://raw.github.com/dcodeIO/ByteBuffer.js/master/dist/ByteBufferAB.min.js -->
    <script src="./ProtoBuf.js"></script>
    <!-- https://raw.github.com/dcodeIO/ProtoBuf.js/master/dist/ProtoBuf.min.js -->
    <script src="./jquery-1.12.0.min.js"></script>
</head>

<body>
    <div class='container'>
        <div class='row row-width'>
            <div id="dropdef" class='span8 col-md-8 columns eight large-8 drop-define'>
                <h2>编辑器</h2>
                <p>选择PB定义文件或者直接拖放到这里</p>
                <div class="row-fluid">
                    <input id="readdef" type="file" class="button tiny" style="width: auto;"/>
                    <select id="pb-sel" style="width: auto; display: none;">
                    </select>
                    <button id="editor-export" class="tiny" style="display: none;">导出</button>
                </div>
                <div id='editor'></div>
            </div>
            <div id="dropdata" class='span4 col-md-4 columns four large-4 drop-define'>
                <h2>JSON输出</h2>
                <p>选择PB数据文件或者直接拖放到这里
                </p>
                <input id="readdata" type="file" class="button tiny"  style="width: auto;"/>
                <button class='btn btn-primary tiny' id='setvalue'>更新到编辑器</button>
                <button class='btn btn-primary tiny' id='json-export'>导出</button>
                <textarea id='output' style='width: 100%; height: 400px; font-family: monospace;' class='form-control'></textarea>
                <h2>Validation</h2>
                <p>This will update whenever the form changes to show validation errors if there are any.</p>
                <textarea id='validate' style='width: 100%; height: 80px; font-family: monospace;' readonly disabled class='form-control'></textarea>
            </div>
        </div>
    </div>
    <script>
    if (typeof dcodeIO === 'undefined' || !dcodeIO.ProtoBuf) {
        throw (new Error("ProtoBuf.js is not present. Please see www/index.html for manual setup instructions."));
    }
    // Initialize ProtoBuf.js
    var ProtoBuf = dcodeIO.ProtoBuf;
    var builder;
    var Message;

    var schema;
    var jsoneditor;
    var editor = $("#editor")[0];
    var output = $("#output");
    var validate = $("#validate");

    function exportData(data) {
        console.log(data);
        var msg = builder.build(Message.name).decodeJSON(data);
        download(msg.toArrayBuffer(), Message.name + ".txt");
    }

    var editorExport = $("#editor-export").on("click", function(evt) {
        var json = jsoneditor.getValue();
        var data = JSON.stringify(json);
        exportData(data);
    });

    var jsonExport = $("#json-export").on("click", function(evt) {
        var data = output.val();
        exportData(data);
    });

    
    function readDefineFile(file) {
        if (!(file instanceof File)) {
            return
        }
        var reader = new FileReader();
        reader.onload = function(e) {
            var result = e.target.result;
            builder = ProtoBuf.loadProto(result);
            var pbSel = $("#pb-sel");
            pbSel.empty();
            $(builder.ns.children).each(function(k, v) {
                var option = $("<option>");
                option.attr("value", k);
                option.html(v.name);
                pbSel.append(option);
            });
            pbSel.show();
            pbSel.trigger("change");
            editorExport.show();
        };
        reader.onerror = function(e) {
            console.log("err", e);
        };
        reader.readAsText(file);
    };

    $("#readdef").on("change", function(evt) {
        var files = evt.target.files[0];
        if(files.length == 0){ 
            return false; 
        }
        readDefineFile(files[0]);        
    });

    function registerDropArea(ele, callback) {
        ele.on({
            dragleave:function(e){
                e.preventDefault();
                ele.removeClass("active");
            }, 
            drop:function(e){
                e.preventDefault(); 
                var files = e.originalEvent.dataTransfer.files;
                if(files.length == 0){ 
                    return false; 
                }
                callback(files[0]);
                ele.removeClass("active");
            },
            dragenter:function(e){
                e.preventDefault();
            }, 
            dragover:function(e){
                e.preventDefault(); 
                ele.addClass("active");
            }
        });
    }

    registerDropArea($("#dropdef"), function(result) {
        readDefineFile(result); 
    });

    $("#pb-sel").on("change", function(evt) {
        var pbIndex = $("#pb-sel").val();
        Message = builder.ns.children[pbIndex];

        function convertField(field) {
            var mappings = {
                'message': 'object',
                'int32': 'integer',
                'double': 'number',
                'string': 'string',
                'bool': 'boolean',
                'enum': 'object'
            };
            var type = mappings[field.type.name] || field.type.name;
            var schema
            if (type === "int64") {
                schema = {
                    type: "object", 
                    title: "int64",
                    properties: {
                        low: {
                            type: 'integer'
                        },
                        high: {
                            type: 'integer'
                        },
                        unsigned: {
                            type: 'boolean'
                        }

                    }
                }
            } else if (type === "object") {
                schema = convertMessage(field.resolvedType);
            } else {
                schema = {};
                schema.type = type;
                schema.id = field.id;
                if (field.defaultValue) {
                    schema.defaultValue = field.defaultValue;
                }
            }
            if (field.repeated) {
                return {
                    type: "array",
                    items: schema
                }
            } else {
                return schema;
            }
        }

        function convertMessage(message) {
            var obj = {};
            obj.title = message.name;
            if (message.className === "Enum") {
                obj.type = "string";
                obj.enum = [];
                $(message.children).each(function(k, v) {
                    obj.enum.push(v.name);
                });
            } else {
                obj.type = "object";
                obj.properties = {};
                $(message.children).each(function(k, v) {
                    obj.properties[v.name] = convertField(v);
                });
            }
            return obj;
        }
        schema = convertMessage(Message);
        var output = JSON.stringify(schema, null, 4);
        console.dir(schema);
        console.log(output);
        reload();
    });
    
    function reload(keep_value) {
        var startval = (jsoneditor && keep_value)? jsoneditor.getValue() : window.startval;
        window.startval = undefined;

        if(jsoneditor) jsoneditor.destroy();
        jsoneditor = new JSONEditor(editor,{
            schema: schema || {},
            startval: startval
        });
        window.jsoneditor = jsoneditor;

        // When the value of the editor changes, update the JSON output and validation message
        jsoneditor.on("change", function() {
            var json = jsoneditor.getValue();

            output.val(JSON.stringify(json, null, 4));

            var validation_errors = jsoneditor.validate();
            // Show validation errors if there are any
            if(validation_errors.length) {
                validate.val(JSON.stringify(validation_errors,null,2));
            }
            else {
                validate.val('valid');
            }
        });
    };
    $("#setvalue").on('click',function() {
        jsoneditor.setValue(JSON.parse(output.val()));
    });
    JSONEditor.defaults.options.object_layout = 'normal';
    JSONEditor.defaults.options.show_errors = 'interaction';
    JSONEditor.defaults.options.theme = 'foundation5';
    JSONEditor.defaults.options.iconlib = 'fontawesome4';

    function decode(buffer) {
        buffer = ProtoBuf.ByteBuffer.wrap(buffer);
        return decodeRaw(buffer);
    }

    function decodeBits(buffer, length) {
        var result = "";
        while (length -- > 0) {
            var b = buffer.readByte() & 0xff;
            var c = b.toString(16)
            result = (c.length == 1 ? "0" + c : c) + result; 
        }
        return "0x" + result;
    }

    function decodeString(buffer, length) {
        var offset = buffer.offset;
        var result = '';
        while (buffer.offset < offset + length) {
            result += buffer.readUTF8String(1);    
        }
        if (buffer.offset == offset + length) {
            return result;
        } else {
            throw Error("Malformat");
        }
    }

    function decodeRaw(buffer, length, expectedGroupEndId) {
        length = typeof length === 'number' ? length : -1;
        var start = buffer.offset, tag, wireType, id, field;
        var result = {};
        while (buffer.offset < start+length || (length === -1 && buffer.remaining() > 0)) {
            tag = buffer.readVarint32();
            wireType = tag & 0x07;
            id = tag >> 3;
            if (wireType === ProtoBuf.WIRE_TYPES.ENDGROUP) {
                if (id !== expectedGroupEndId)
                    throw Error("Illegal group end indicator for "+this.toString(true)+": "+id+" ("+(expectedGroupEndId ? expectedGroupEndId+" expected" : "not a group")+")");
                expectedGroupEndId = undefined;
                return result;
            }
            var msg = null;
            switch (wireType) {
                case ProtoBuf.WIRE_TYPES.VARINT:
                    msg = buffer.readVarint32();
                    break;
                case ProtoBuf.WIRE_TYPES.BITS32:
                    msg = decodeBits(buffer, 4);
                    break;
                case ProtoBuf.WIRE_TYPES.BITS64:
                    msg = decodeBits(buffer, 8);
                    break;
                case ProtoBuf.WIRE_TYPES.LDELIM:
                    var len = buffer.readVarint32();
                    var offset = buffer.offset;
                    // TODO
                    var isString = true;
                    try {
                        msg = decodeString(buffer, len);
                    } catch (e) {
                        isString = false;
                    }
                    if (isString && buffer.offset != offset + len) {
                        isString = false;
                    } else if (isString) {
                        for (var i in msg) {
                            var c = msg.charCodeAt(i);
                            if (c < 0x20 && c != 0xa && c != 0x9) {
                                isString = false;
                                break;
                            }
                        }
                    }
                    if (!isString) {
                        buffer.offset = offset
                        msg = decodeRaw(buffer, len, expectedGroupEndId);
                    }
                    
                    break;
                case ProtoBuf.WIRE_TYPES.STARTGROUP:
                    msg = decodeRaw(buffer, -1, id);
                    break;
                default:
                    throw Error("Illegal wire type for unknown field "+id+" in "+this.toString(true)+"#decode: "+wireType);
            }
            if (msg) {
                if (result[id] instanceof Array) {
                    result[id].push(msg);
                } else if (result[id]) {
                    result[id] = [result[id], msg];
                } else {
                    result[id] = msg;
                }
            } else {
                var a;
            }
        }
        if (expectedGroupEndId) {
            throw Error("Illegal wire type for unknown field "+id+" in "+this.toString(true)+"#decode: "+wireType);
        }
        return result;
    }

    function readDataFile(file) {
        if (!(file instanceof File)) {
            return
        }
        var reader = new FileReader();
        reader.onload = function(e) {
            var result = e.target.result;
            var msg = null;
            if (builder) {
                msg = builder.build(Message.name).decode(result);
            } else {
                msg = decode(result);
            }
            var jsonString = JSON.stringify(msg, null, 4);
            output.val(jsonString);
            console.log(msg);
        };
        reader.onerror = function(e) {
            console.log("err", e);
        };
        reader.readAsArrayBuffer(file);
    };

    registerDropArea($("#dropdata"), function(result) {
        readDataFile(result); 
    });

    $("#readdata").on("change", function(evt) {
        var files = evt.target.files[0];
        if(files.length == 0){ 
            return false; 
        }
        readDataFile(files[0]);        
    });

    function download( data, fileName, fileType ) {
        var blob, url, a;
        var stamp = new Date().getTime();
        fileType = ( fileType || "text/plain;charset=UTF-8" );
        fileName = ( fileName || "data.txt" );
        blob = new Blob( [data], { type: fileType } );
        
        if ( blob ) {
            url = window.URL.createObjectURL( blob );
            a = document.createElement( "a" );
            document.body.appendChild( a );
            a.style = "display: none";
            a.href = url;
            a.download = fileName;
            a.click();
            a.remove();
            window.URL.revokeObjectURL( url );
        } else {
            console.log("no blob to download")
        }
    }


    if (true) {
        builder = ProtoBuf.loadProtoFile("./HomePageMessage.proto");
        var pbSel = $("#pb-sel");
        pbSel.empty();
        $(builder.ns.children).each(function(k, v) {
            var option = $("<option>");
            option.attr("value", k);
            option.html(v.name);
            pbSel.append(option);
        });
        pbSel.show();
        pbSel.val("8");
        pbSel.trigger("change");
        editorExport.show();
    }
    if (false) {
        function getArrayBuffer(url, callback) {
            var xhr = new XMLHttpRequest();
            xhr.open('GET', url);
            xhr.responseType = 'arraybuffer';
            xhr.onload = function() {
                if (this.status == 200) {
                    callback(xhr.response);
                } else {
                    console.error('Error while requesting', file, this);
                }
            };
            xhr.send();
        }
        getArrayBuffer("./HomePageMessage.txt", function(result) {
            var result = decode(result);
            console.log(JSON.stringify(result, null, 4));
        });
    }
    </script>
</body>

</html>